package gat.api.config.aspect;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import gat.application.log.LogService;
import gat.common.util.CurrentThread;
import gat.common.util.RequestHolder;
import gat.dto.req.log.LogReqDto;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;

@Component
@Aspect
@Slf4j
public class LogAspect {

    @Autowired
    private LogService logService;

    ThreadLocal<Long> currentTime = new ThreadLocal<>();

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(gat.api.config.annotation.Log)")
    public void logPointcut() {
        // 该方法无方法体,主要为了让同类中其他方法使用此切入点
    }

    /**
     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
     *
     * @param joinPoint join point for advice
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        currentTime.set(System.currentTimeMillis());
        result = joinPoint.proceed();

        LogReqDto logReqDto = new LogReqDto();
        logReqDto.setLogType("INFO");
        logReqDto.setTime(System.currentTimeMillis() - currentTime.get());

        currentTime.remove();
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        saveLog(joinPoint, logReqDto, signature, method);
        return result;
    }

    /**
     * 配置异常通知
     *
     * @param joinPoint join point for advice
     * @param e exception
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        LogReqDto logReqDto = new LogReqDto();
        currentTime.set(System.currentTimeMillis());
        logReqDto.setLogType("ERROR");
        logReqDto.setTime(System.currentTimeMillis() - currentTime.get());

        currentTime.remove();
        logReqDto.setExceptionDetail(getStackTrace(e));
        HttpServletRequest request = RequestHolder.getHttpServletRequest();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        saveLog(joinPoint, logReqDto, signature, method);
    }

    /**
     * 方法抽取
     * @param joinPoint
     * @param logReqDto
     * @param signature
     * @param method
     */
    private void saveLog(JoinPoint joinPoint, LogReqDto logReqDto, MethodSignature signature, Method method) {
        try {
            gat.api.config.annotation.Log aopLog = method.getAnnotation(gat.api.config.annotation.Log.class);

            // 方法路径
            String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";

            StringBuilder params = new StringBuilder("{");
            //参数值
            Object[] argValues = joinPoint.getArgs();
            //参数名称
            String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
            if (argValues != null) {
                for (int i = 0; i < argValues.length; i++) {
                    params.append(" ").append(argNames[i]).append(": ").append(argValues[i]);
                }
            }
            // 描述
            if (logReqDto != null) {
                logReqDto.setDescription(aopLog.value());
            }
            //类型 0-后台 1-前台
            logReqDto.setType(0);
            if (getUid() != null) {
                logReqDto.setUserCode(CurrentThread.getCurrentUser().getUserCode());
                logReqDto.setUsername(CurrentThread.getCurrentUser().getUsername());
                logReqDto.setPhone(CurrentThread.getCurrentUser().getPhone());
                logReqDto.setNickname(CurrentThread.getCurrentUser().getNickname());
            }
            assert logReqDto !=null;
            logReqDto.setRequestIp(getIp(RequestHolder.getHttpServletRequest()));

            logReqDto.setAddress(getCityInfo(logReqDto.getRequestIp()));
            logReqDto.setMethod(methodName);
            logReqDto.setParams(params.toString() + " }");
            logService.save(logReqDto);
        } catch (Exception ex) {
            log.error(ex.getMessage());
        }

    }



    public Long getUid(){
        try {
            return CurrentThread.getCurrentUser().getId();
        }catch (Exception e){
            return null;
        }
    }

    /**
     * 获取ip地址
     */
    public static String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0
//                || UNKNOWN.equalsIgnoreCase(ip)
        ) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0
//                || UNKNOWN.equalsIgnoreCase(ip)
        ) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0
//                || UNKNOWN.equalsIgnoreCase(ip)
        ) {
            ip = request.getRemoteAddr();
        }
        String comma = ",";
        String localhost = "127.0.0.1";
        if (ip.contains(comma)) {
            ip = ip.split(",")[0];
        }
        if  (localhost.equals(ip))  {
            // 获取本机真正的ip地址
            try {
                ip = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
        return ip;
    }

    /**
     * 根据ip获取详细地址
     */
    public static String getCityInfo(String ip) {
        try {
            String api = String.format("http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true",ip);
            JSONObject object = JSONUtil.parseObj(HttpUtil.get(api));
            return object.get("addr", String.class);
        } catch (Exception ex) {
            log.error(ex.getMessage());
        }
        return "";

    }

    public static String getStackTrace(Throwable throwable){
        StringWriter sw = new StringWriter();
        try (PrintWriter pw = new PrintWriter(sw)) {
            throwable.printStackTrace(pw);
            return sw.toString();
        }
    }
}

